package io.sundial.console;

import io.sundial.core.context.Clazz;
import io.sundial.core.context.Context;
import io.sundial.core.context.RoleException;
import io.sundial.core.context.Supplier;
import io.sundial.core.context.exception.RoleNotFoundException;
import io.sundial.core.context.exception.RoleNotSuppliedException;
import io.sundial.core.context.exception.RoleNotUniqueException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;

/**
 * Spring Boot 上下文
 *
 * @author Payne 646742615@qq.com
 * 2019/1/4 15:49
 */
@Component
public class ConsoleContext implements Context, ApplicationContextAware {
    private ApplicationContext applicationContext;

    @SuppressWarnings("unchecked")
    @Override
    public <T> T get(String name) throws RoleException, ClassCastException {
        try {
            return (T) applicationContext.getBean(name);
        } catch (NoSuchBeanDefinitionException e) {
            throw new RoleNotFoundException(e);
        } catch (Exception e) {
            throw new RoleException(e);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T get(String name, Supplier<T> supplier) throws RoleException, ClassCastException {
        try {
            return (T) applicationContext.getBean(name);
        } catch (NoSuchBeanDefinitionException e) {
            T supplied = supplier != null ? supplier.supply(this) : null;
            if (supplied == null) {
                throw new RoleNotSuppliedException(e);
            }
            ((SingletonBeanRegistry) applicationContext).registerSingleton(name, supplied);
            return supplied;
        } catch (Exception e) {
            throw new RoleException(e);
        }
    }

    @Override
    public <T> T get(Class<T> type) throws RoleException {
        try {
            return applicationContext.getBean(type);
        } catch (NoUniqueBeanDefinitionException e) {
            throw new RoleNotUniqueException(e);
        } catch (NoSuchBeanDefinitionException e) {
            throw new RoleNotFoundException(e);
        } catch (Exception e) {
            throw new RoleException(e);
        }
    }

    @Override
    public <T> T get(Class<T> type, Supplier<T> supplier) throws RoleException {
        try {
            return applicationContext.getBean(type);
        } catch (NoUniqueBeanDefinitionException e) {
            throw new RoleNotUniqueException(e);
        } catch (NoSuchBeanDefinitionException e) {
            T supplied = supplier != null ? supplier.supply(this) : null;
            if (supplied == null) {
                throw new RoleNotSuppliedException(e);
            }
            ((SingletonBeanRegistry) applicationContext).registerSingleton(type.getName(), supplied);
            return supplied;
        } catch (Exception e) {
            throw new RoleException(e);
        }
    }

    @Override
    public <T> T get(Clazz<T> type, Supplier<T> supplier) throws RoleException {
        try {
            return applicationContext.getBean(type.getRawClass());
        } catch (NoUniqueBeanDefinitionException e) {
            throw new RoleNotUniqueException(e);
        } catch (NoSuchBeanDefinitionException e) {
            T supplied = supplier != null ? supplier.supply(this) : null;
            if (supplied == null) {
                throw new RoleNotSuppliedException(e);
            }
            ((SingletonBeanRegistry) applicationContext).registerSingleton(type.getRawClass().getName(), supplied);
            return supplied;
        } catch (Exception e) {
            throw new RoleException(e);
        }
    }

    @Override
    public Class<?> check(String name) throws RoleException {
        try {
            return applicationContext.getType(name);
        } catch (NoSuchBeanDefinitionException e) {
            throw new RoleNotFoundException(e);
        } catch (Exception e) {
            throw new RoleException(e);
        }
    }

    @Override
    public <T> Map<String, T> fetch(Class<T> type) throws RoleException {
        try {
            return applicationContext.getBeansOfType(type);
        } catch (Exception e) {
            throw new RoleException(e);
        }
    }

    @Override
    public Iterator<String> iterator() {
        return Arrays.asList(applicationContext.getBeanDefinitionNames()).iterator();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
}
